/**
  ETFRoot project, Anyang Normal University && IMP-CAS
  \class ETFDC
  \brief for DCs downstream the dipole magnet. The construction of the DC depends
  on the input yaml config file.
  \author SUN Yazhou, asia.rabbit@163.com
  \since 2021-10-28
  \date 2021-12-10 last modified
  \attention
  changelog
  <table>
  <tr>  <th>Date         <th>Author      <th>Description                   </tr>
  <tr>  <td>2021-10-28   <td>Asia Sun    <td>file created                  </tr>
  </table>

  \copyright Copyright (c) 2021 Anyang Normal U. && IMP-CAS with LGPLv3 LICENSE
*/

#include <iostream>
#include <cstring>
#include <TClonesArray.h>
#include <TGraph.h>
#include <string>
#include "FairRootManager.h"
#include "ETFDetectorManager.h"
#include "ETFTrackManager.h"
#include "ETFDCHit.h"
#include "ETFMath.h"
#include "ETFMsg.h"
#include "ETFDC.h"
#include "ETFHPTDCChannel.h"
#include "ETFVTrack.h"
#include "ETFParaManager.h"
#include "ETFYaml.h"
#include "ETFPostMagTracker.h"

using std::cout;
using std::endl;
using std::string;

ClassImp(ETFDC);

static constexpr double DEGREE = ETFMath::DEGREE();
static constexpr double DEG30 = 30.*DEGREE;

ETFDC::ETFDC(const char *name) : ETFVDC(name), type{0}, fPhiAvrg(-9999.){
  NL = 80;

  ETFParaManager *pm = ETFParaManager::Instance();
  const YAML::Node &chIdConfig = (*pm->GetChIdPara())[name];
  const char *xuv[] = {"X", "U", "V"};
  for(const char *c : xuv){
    if(chIdConfig[c]){
      type[2] = 0;
      for(const auto &chid : chIdConfig[c]){
        FillCableChMap(chid.as<int>());
        type[2]++; // cable id
      } // end for over cables
    } // end if
    else ETFMsg::Warn("ETFDC", "ctor: No %c chId in yaml config file", c);
    type[0]++; // XUV
  } // end for over XUV
  // pass user trig bound parameters //
  const YAML::Node &trigBConfig = (*pm->GetMisPara())["dcTrigB"];
  if(trigBConfig){
    fTrigLB = trigBConfig[0].as<double>();
    fTrigHB = trigBConfig[1].as<double>();
  } // end if

  for(int i = 0; i < 3; i++) for(int j = 0; j < 2; j++){
    fHits[i][j] = new TClonesArray("ETFDCHit");
    char name_cl[128]; sprintf(name_cl, "%s%s%d", name, xuv[i], j);
    fHits[i][j]->SetName(name_cl);
  } // end embedded for
} // end ctor

ETFDC::~ETFDC(){
  for(auto &h : fHits) for(auto &l : h) if(l) delete l;
} // end the dtor

void ETFDC::Register(){
  static FairRootManager *m = FairRootManager::Instance();
  for(auto &p : fHits) for(auto &l : p) if(l) m->Register(l->GetName(), "DetHits", l, true);
} // end member function Register

TClonesArray *ETFDC::GetHitsArray(DCType typeDC, int x12){
  if(x12 < 0 || x12 >= 3) ETFMsg::Error("ETFDC", "GetHitsArray: x12 out of range");
  return fHits[short(typeDC)%3][x12%2];
} // end member function GetHitsArray

// initialize the detector
void ETFDC::Initialize(){
  ETFDetector::Initialize();
  for(auto &h : fHits) for(auto &l : h) l->Clear();
} // end member function Initialize

// add the data channel to the detector
void ETFDC::Assign(const ETFHPTDCChannel *ch){
  int uid = ETFDetectorManager::Instance()->GetUID(ch->GetChannelId());
  if(fDetId != (uid & 0xFF)) ETFMsg::Error("ETFDC", "Assign: not from this DC.");
  DCType typeDC;
  switch(uid>>8 & 0x3){
    case 0: typeDC = DCType::kX; break;
    case 1: typeDC = DCType::kU; break;
    case 2: typeDC = DCType::kV; break;
    default: ETFMsg::Error("ETFDC", "Assign: wrong DC type"); break;
  } // end switch
  int wireId = NL*(uid>>10 & 0x1) + (uid>>11 & 0x7F);
  AddHit(ch, wireId, typeDC);
} // end member function Assign

// fill channel map for a 32-ch LVDS cable
// type[4]; ///< detId, XUV, X1-X2, [cableId], wireId
void ETFDC::FillCableChMap(int chId0){
  if(chId0 < 0) return;
  for(int i = 0; i < 2; i++) for(int j = 0; j < 16; j++)
    //            channel Id              X-U-V      X1-2      wireId: 0-79
    SetChIdMap(chId0-i*16-j+31, fDetId+(type[0]<<8)+(i<<10)+((type[2]*16+j)<<11));
} // end member function FillCableChMap

void ETFDC::AddHit(const ETFHPTDCChannel *ch, int wireId, DCType typeDC){
  short xuv = short(typeDC);
  char cc; // = X, U or V according to xuv
  static constexpr double cos30 = cos(DEG30);
  static constexpr double tan30 = tan(DEG30);
  const int x12 = wireId / NL, id = wireId % NL;
  ETFDCHit *hit =
    dynamic_cast<ETFDCHit *>(fHits[xuv][x12]->ConstructedAt(fHits[xuv][x12]->GetEntries()));
  hit->Initialize();
  hit->SetDataChannel(ch);
  hit->SetLayerId(GetDCId()*2+x12); // 0-1-2-3-4-5: DC0X1-X2--DC1X1-X2--DC2X1-X2
  hit->SetWireId(id);
  hit->SetDelay(fDelay);
  // set position in local frame, (x,y,z) is the projection of the wire in the layer's norml plane
  static constexpr double x0 = -393.; // 7. - 5.*80, 80: N of sense wire per layer
  if(DCType::kX == typeDC){
    cc = 'x';
    hit->SetX(x0+10.*id-4.*x12);
    hit->SetY(0.);
    hit->SetZ(10.*x12);
  } // end if
  if(DCType::kU == typeDC){
    cc = 'u';
    hit->SetX((x0+10.*id-4.*x12)*cos30);
    hit->SetY(-hit->GetX()*tan30);
    hit->SetZ(10.*x12-40.8);
  } // end if
  if(DCType::kV == typeDC){
    cc = 'v';
    hit->SetX((x0+10.*id-4.+4.*x12)*cos30);
    hit->SetY(hit->GetX()*tan30);
    hit->SetZ(10.*x12+40.8);
  } // end if
  char name[128]; sprintf(name, "%s_%c%d_id%d", GetName(), cc, x12, id);
  hit->SetName(name);

  TransformToLabRef(hit);
  SetHitProjection(hit, typeDC);
} // end member function AddHit

// set the wire's projection to its normal plane
void ETFDC::SetHitProjection(ETFDCHit *hit, DCType typeDC){
  const double *p = hit->GetPosition();
  if(DCType::kX == typeDC){
    hit->SetProjection(p);
    return; // X wires does not need backward rotation
  } // end if

  if(-9999. == fPhiAvrg)
    fPhiAvrg = ETFTrackManager::Instance()->GetPostMagTracker()->GetPhiAvrg();
  const double phi = fPhiAvrg; // just for convenience
  double theta = 0.;
  if(DCType::kU == typeDC) theta = -DEG30;
  if(DCType::kV == typeDC) theta =  DEG30;
  double proj[3]{}; // projection of the wire center position to the wire's normal plane

  const double cp = cos(phi), sp = sin(phi);
	const double ct = cos(theta), st = sin(theta);
	// Au = Rz[-theta].Ry[-phi].A;
	proj[0] = ct*cp  *p[0] +st *p[1] -ct*sp *p[2];
	proj[1] = -st*cp *p[0] +ct *p[1] +st*sp *p[2];
	proj[2] = sp     *p[0] +0. *p[1] +cp    *p[2];

  hit->SetProjection(proj);
} // end member function SetHitProjection

// r = r(t); drift distance - drift time relation
double ETFDC::SpaceTimeRelation(double t){
  // note that the following parameterization is for HV = 1030V
  // usually STR at other HVs are so different as worth special treatment
  // be it a garfield simulation, or user-refined tuning based on experiment data analysis
  static constexpr int POLN = 6;
  static const double p[POLN] =
    {-0.0687818, 0.0892494, -0.0011655, 1.0439e-05, -4.71409e-08, 8.07863e-11};
  double r = 0.;
  if(ETFMath::Within(t, 0., 190.)) for(int i = POLN; i--;) r += p[i]*pow(t, i);
  if(r < 0. || t < -10.) r = 0.;
  if(r > 5. || t > 190.) r = 5.;

  // static constexpr int POLN = 8;
  // static const double p[POLN] =
  //   {0.187685, 0.0764427, -0.000885014, 7.50134e-06, -3.46448e-08, 7.3463e-11,
  //     -4.36461e-14, -3.25979e-17};
  // double r = 0.;
  // if(ETFMath::Within(t, 0., 190.)) for(int i = POLN; i--;) r += p[i]*pow(t, i);
  // if(r < 0. || t < -10.) r = 0.;
  // if(r > 5. || t > 335.) r = 5.;

  return r < 0. ? 0. : r;
} // end member function SpaceTimeRelation

/// \retval false: if obtained drift time out of raColornge
/// NOTE that if trk has non-FitStatus::kUNFITTED FitStatus, this method
/// would do nothing, as drift distance only changes with drift time,
/// whose changes would ensure fFitStatus be set to FitStatus::kUNFITTED
bool ETFDC::SetDriftDistance(ETFVTrack *trk){
  // drift distance already assigned
  if(trk->GetFitStatus() != FitStatus::kUNFITTED) return true;

  for(auto &p : trk->GetHits()){
    ETFDCHit *h = p.second;
    // TODO: user input configuration expected here to confine drift time range
    if(!ETFMath::Within(h->GetDriftTime(), -30., 500.)) return false; // false hit
    h->SetDriftDistance(SpaceTimeRelation(h->GetDriftTime()));
  } // end for over hits
  return true;
} // end member function SetDriftDistance

// add strip to fGDumb
void ETFDC::ConstructGraph(){
  if(fGDumb->GetN()) return; // has called

  fGDumb->SetMarkerStyle(7); fGDumb->SetMarkerColor(15); // gray
  for(int i = 0; i < 2; i++) for(int j = 0; j < NL; j++){
    double p[3] = {-393.+10.*j-4.*i, 0., 10.*i};
    TransformToLabRef(p);
    fGDumb->SetPoint(i*NL+j, p[2], p[0]);
  } // end for over i
} // end member function ConstructGraph

void ETFDC::DrawHits(){
  for(auto &h : fHits[0]) if(h) for(const auto &p : *h)
    dynamic_cast<ETFDCHit *>(p)->Draw();
} // end member function DrawHits

void ETFDC::Print(){
  ETFDetector::Print();
  static const char *dcType[] = {"DCType::kX", "DCType::kU", "DCType::kV"};
  static const char *x12[] = {"X1", "X2"};
  int i = 0, j = 0;
  cout << "Number of sense wires per layer: " << NL << endl;
  for(auto &h : fHits){
    cout << "\033[33;1m___________DCType: " << dcType[i++] << "___________\033[0m" << endl;
    j = 0;
    for(auto &l : h){
      cout << "\033[31;1m-------------- x12: " << x12[j++] << " --------------\033[0m" << endl;
      for(auto p : *l) dynamic_cast<ETFDCHit *>(p)->Print();
    } // end for over x1-x2
  } // end for over xuv
} // end member function Print
